Skip to content

S14-05 SSR-Next13

[TOC]

概述

什么是 Next.js

Next.js 是一个React 框架,支持CSRSSRSSGISR (Incremental Static Regeneration)等渲染模式。

Next.js 提供了创建 Web 应用程序的构建块,比如:用户界面、路由、数据获取、渲染模式、后端服务等等。

Next.js 不但处理 React 所需的工具和配置,还提供额外的功能和优化,比如:

  • UI 构建, CSR、SSR、SSG、ISR 渲染模式,Routing、Data Fetching 等等。

中文官网:https://www.nextjs.cn/docs/getting-started

英文官网:https://nextjs.org/docs/getting-started

image-20240911160746941

发展史

  • Next.js 于 2016 年 10 月 25 日首次作为开源项目发布在 GitHub 上,最初是基于六个原则开发的:

    • 开箱即用、无处不在的 JS、所有函数用 JS 编写、自动代码拆分和服务器渲染、可配置数据获取、预期请求和简化部署。
  • Next.js 2.0 于 2017 年 3 月发布,改进后的版本让小型网站的工作变得更加容易,还提高了构建和热模块替换效率。

  • 7.0 版于 2018 年 9 月发布,改进了错误处理并支持 React 的上下文 API。升级到了webpack4

  • 8.0 版于 2019 年 2 月发布,第一个提供 Serverless 部署的版本。

  • 2020 年 3 月发布的 9.3 版包括各种优化和全局 Sass和 CSS 模块支持。

  • 2020 年 7 月 27 日,Next.js 9.5 版发布,增加了增量静态再生成重写重定向支持等新功能。

  • 2021 年 6 月 15 日,Next.js 版本 11 发布,其中包括:Webpack5 支持。

  • 2021 年 10 月 26 日,Next.js 12 发布,添加了 Rust 编译器,使编译速度更快。

  • 2022 年 10 月 26 日,Vercel 发布了 Next.js 13

    • 带来了一种新的路由模式,增加了 app 目录 、布局、服务器组件和一组新的数据获取方法等。
    • 编译和压缩等由 Babel + Terser 换为 SWC( Speedy Web Compiler ),构建工具增加了 Turbopack

特点

  • 开箱即用,快速创建:

    • Next.js 已经帮我集成好了各种技术栈,比如:React、webpack、路由、数据获取、SCSS、TypeScript 等。

    • 也提供了专门的脚手架:create-next-app

  • 约定式路由:

    • Next.js 和 Nuxt3 一样,所有的路由都是根据 pages 目录结构自动生成。但在 Next.js 13 beta 版本增加了 app 目录。
  • 内置 CSS 模块和 Sass 支持:

    • 自从 Next.js 9.3 以后就内置了 CSS 模块和 Sass 支持,也是开箱即用。
  • 全栈开发能力:

    • Next.js 不但支持前端开发,还支持编写后端代码,比如:可开发登录验证、存储数据、获取数据等接口。
  • 多种渲染模式: 支持 CSR、SSR、SSG、ISR 等渲染模式,当然也支持混合搭配使用。

  • 利于搜索引擎优化:

    • Next.js 支持使用服务器端渲染,同时它也是一个很棒的静态站点生成器,非常利于 SEO 和首屏渲染。

对比 Nuxt3

相同点:

  • 利于搜索引擎优化,都能提高首屏渲染速度
  • 零配置,开箱即用
  • 都支持目录结构即路由、支持数据获取、支持TypeScript
  • 服务器端渲染静态网站生成客户端渲染
  • 都需要 Node.js 服务器,支持全栈开发

区别:

  • Nuxt3 使用的是Vue 技术栈:Vue、webpack、vite、h3、nitro、node.....

    Next.js 使用的是React 技术栈:React 、webpack 、express 、node.....

  • Nuxt3 支持组件、组合 API、Vue API 等自动导入

    Next.js 不支持

  • Next.js 社区生态、资源和文档都会比 Nuxt3 友好(star 数: Nuxt3 -> 41.6k 和 Next.js -> 96.8k )

如何选择:

  • 首先根据自己擅长的技术栈来选择,擅长 Vue 选择 Nuxt3,擅长 React 选择 Next.js
  • 需要更灵活的,选择Next.js
  • 需要简单易用、快速上手的,选择Nuxt3

基础

环境搭建 create-next-app

前置环境:

  • Node.js:18.18 以上
  • 前置环境:window 下可以用其随附的 Git Bash 终端命令
  • 文本编辑器:Visual Studio Code

新建项目:

1、创建一个项目,项目名不支持大写

sh
# npx(推荐)
npx create-next-app@latest

# pnpm(推荐)
pnpm dlx create-next-app@latest --typescript

# yarn
yarn create next-app --typescript

# pnpm
pnpm create next-app –-typescript

# npm
npm i create-next-app@latest –g && create-next-app

image-20241012220054039

image-20241029215631240

2、通过pnpm dev运行项目

目录结构

image-20240911160815710

入口文件 _app.tsx

_app.tsx 是项目的入口组件,主要作用:

  • 扩展自定义的布局 Layout
  • 引入全局样式
  • 引入 Redux 状态管理
  • 引入主题组件等等
  • 全局监听客户端路由的切换

image-20240911160827051

image-20240911160835528

配置

路径别名 tsconfig.json

Next.js 默认是没有配置路径别名的,我们可以在 ts.config.json 中配置模块导入的别名:

  • baseUrl :配置允许直接从项目的根目录导入,比如: import Button from 'components/button'
  • paths:允许配置模块别名,比如: import Button from '@/components/button'

image-20240911160850278

注意:如生效可以重启编辑器

next15: next15默认已经配置好了路径别名

json
{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

环境变量

定义环境变量 .env

定义方式:

  • .env:所有环境下生效的默认设置
  • .env.development:执行 next dev 时加载并生效
  • .env.production : 执行 next start 时加载并生效
  • .env.xxx.local :始终覆盖上面文件定义的默认值。常用于存储敏感信息。所有环境生效,通常只需一个.env.local 文件就够了。

语法:

  • 大写单词,多个单词使用下划线,比如:DB_HOST=localhost
  • 添加 NEXT_PUBLIC_ 前缀会额外暴露给浏览器,比如:NEXT_PUBLIC_ANALYTICS_ID=aaabbbccc
  • 支持变量,例如 $PORT
js
// 只能在服务端获取
NAME = localhost
PORT = 8888
HOSTNAME = http://127.0.0.1

// 添加NEXT_PUBLIC_前缀的变量可以在客户端获取
NEXT_PUBLIC_BASE_URL = http://localhost:9999

// 支持变量$XXX
NEXT_PUBLIC_URL = http://$NAME:$PORT

获取环境变量 process.env

获取: process.env.xxx

.env 文件中定义环境变量会加载到 process.env 中。两端都可直接通过 process.env.xxx 访问使用(不支持解构)(没有代码提示)。

1、默认环境变量只能在服务端访问

image-20241014115126998

2、添加NEXT_PUBLIC_前缀的变量可以在客户端访问

image-20241014115526288


▸ 判断客户端、服务端: typeof window === 'object'

Next中通过typeof window === 'object'是否存在window对象的方式判断客户端、服务端环境。

注意: Next中不支持process.serverprocess.client的方式判断运行环境。

image-20241014114359415


▸ 判断开发环境、生产环境: process.env.NODE_ENV

Next中通过process.env.NODE_ENV 判断开发环境、生产环境。development:开发环境;production:生产环境。


注意:

  • 由于 .env、.env.development 和 .env.production 文件定义了默认设置,需提交到源码仓库中。
  • .env.xxx.local 应当添加到 .gitignore 中,因为这类文件是需要被忽略的。

image-20240911160908411

Next配置 next.config.ts

next.config.ts 配置文件位于项目根目录,可对 Next.js 进行自定义配置,比如,可以进行如下配置:

  • reactStrictMode: 是否启用严格模式,辅助开发,避免常见错误,例如:可以检查过期 API 来逐步升级
  • env:配置环境变量,配置完需要重启
    • 会添加到 process.env.xx 中
    • 配置的优先级: next.config.js 中的 env > .env.local > .env
  • basePath:要在域名的子路径下部署 Next.js 应用程序,您可以使用 basePath 配置选项。
    • basePath:允许为应用程序设置 URl路由前缀
    • 例如 basePath=/music, 即用 /music 访问首页,而不是默认 /
  • images:配置图像优化,包括域名白名单。
  • swcMinify: 用 Speedy Web Compiler 编译和压缩技术,而不是 Babel + Terser 技术
  • experimental:启用实验性功能,例如 appDir

更多配置: https://nextjs.org/docs/api-reference/next.config.js/introduction


▸ 示例:

ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  // 启用严格模式
  reactStrictMode: true,

  // 配置图片 URL 的白名单
  images: {
    remotePatterns: [
        {
            protocol: "https",
            hostname: "**.music.126.net"
        }
    ]
  },
    
  // 设置环境变量
  env: {
    CUSTOM_ENV: 'value'
  },

  // 启用实验性功能
  experimental: {
    appDir: true
  }
    
  // 设置路由前缀
  basePath: '/music'
}

export default nextConfig

内置组件

API

Next.js 框架也提供了几个内置组件,常用的有:

  • <Head>:用于将新增的标签添加到页面的 head 标签中,从 next/head 中导入。

    • html
      import Head from 'next/head';
      
      const MyPage = () => {
        return (
          <>
            <Head>
              <title>我的页面标题</title>
              <meta name="description" content="这是我的页面描述" />
              <link rel="icon" href="/favicon.ico" />
            </Head>
            <main>
              <h1>欢迎来到我的页面!</h1>
            </main>
          </>
        );
      };
      
      export default MyPage;
  • <Link>:可以启用客户端的路由切换,从 next/link 导入。

    • 属性:

    • hrefstring | {pathname:'' , query : {}},要导航到的路径或 URL。

    • replace?boolean默认:false,是否替换当前 url 页面,而不是将新的 url 添加到堆栈中。

    • target?_blank | _self | _parent | _top,指定何种方式显示新页面,同a标签中的target一样。

    • as?string,在浏览器的URL栏显示的路径的别名。

    • prefetchboolean默认:false,当 <Link> 组件进入用户的视区(最初或通过滚动)时,将发生预取。预取仅在 production 中启用

    • 其他:scroll、shallow、locale等

    • tsx
      <Link href="/dashboard">Dashboard</Link>
      <Link href={{ pathname: '/about', query: { name: 'test' }, }}>About</Link>
      
      <Link href="/dashboard" replace>Dashboard</Link>
      
      <Link href="/dashboard" prefetch={false}> Dashboard </Link>
  • <Image>:内置的图片组件,对img的增强。从 next/image 导入。

    • 属性:

    • srcstring,引入图片资源。

      • 引入本地图片时,会自动确认图片的高度
      • 引入外部图片时,需手动设置宽高,以及配置资源地址白名单
    • widthpx,图片宽度,不支持百分比单位

    • heightpx,图片高度,不支持百分比单位

    • altstring,在图像无法显示时的替代文本。

    • fill?boolean默认:true,让图片填充父容器大小,父容器必须分配 position,默认为absolute

    • priority?boolean默认:false,是否将图像视为高优先级,并且预加载(preload)。

      • loading=lazy冲突,不能同时使用
      • 应该对检测为LCP(最大内容绘制)元素的任何图像使用 priority 属性。
      • 仅当图像在首屏可见时,才应使用。
    • placeholderblur | empty | data:image/...默认:empty,加载图像时使用的占位符。

      • empty时,则在加载图像时将没有占位符,只有空白区域。
      • blur 时,blurDataURL属性将用作占位符。
      • 对于动态图像,必须提供 blurDataURL 属性。
      • data:image/...时,则该dataURL 将在加载图像时用作占位符。
    • tsx
      <Image
          src="/profile.png"
          width={500}
          height={500}
          alt="Picture of the author"
          fill={true}
          priority={true}
          placeholder="blur"
      />
  • <Script>:将一个script标签添加到页面的body中(不支持在_document.js 中使用),从 next/script 中导入。

    • 属性:

    • srcstring,指定外部脚本的 URL 的路径字符串。可以是绝对外部 URL 或内部路径。

    • tsx
      import Script from 'next/script'
       
      export default function Dashboard() {
        return (
          <>
            <Script src="https://example.com/script.js" />
          </>
        )
      }

<Head> 组件用于管理页面的 <head> 部分,可以在其中设置文档的元数据,如标题、描述、关键字、样式表和脚本等。使用 <Head> 组件可以确保每个页面都可以独立地定义自己的 <head> 内容

如果想要给所有页面统一添加的,那需在 pages 目录下新建 _document.js 文件来定制 HTML 页面。

作用: 方便 SEO 以及添加一些外部资源。

用法:

▸ 基本使用

tsx
import Head from 'next/head';

const MyPage = () => {
  return (
    <>
      <Head>
        <title>我的页面标题</title>
        <meta name="description" content="这是我的页面描述" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main>
        <h1>欢迎来到我的页面!</h1>
      </main>
    </>
  );
};

export default MyPage;

▸ 避免重复标签: key

为避免<Head>中出现重复的标签,您可以使用 key 属性,这将确保标签只呈现一次。

tsx
import Head from 'next/head'
 
function IndexPage() {
  return (
    <div>
      <Head>
        <title>My page title</title>
        <meta property="og:title" content="My page title" key="title" />
      </Head>
      <Head>
        <meta property="og:title" content="My new title" key="title" />
      </Head>
      <p>Hello world!</p>
    </div>
  )
}
 
export default IndexPage

说明:上述2个meta具有相同的key,只会渲染第二个meta标签

注意: <title><base> 标签会由 Next.js 自动检查是否有重复项,因此不需要使用 key。

_document.js

可以在~/pages/_document.js中配置 Head 的全局配置。

特性:

  • _document文件中的 Head 中不能包括<title>标签。
  • _document文件中不支持<Script>标签。

image-20241014125519729

Image

<Image>:内置的图片组件,对img的增强。从 next/image 导入。

  • srcstring,引入图片资源。

    • 引入本地图片时,会自动确认图片的高度
    • 引入外部图片时,需手动设置宽高,以及配置资源地址白名单
  • widthpx,图片宽度,不支持百分比单位

  • heightpx,图片高度,不支持百分比单位

  • altstring,在图像无法显示时的替代文本。

  • fill?boolean默认:true,让图片填充父容器大小,父容器必须分配 position,默认为absolute

  • priority?boolean默认:false,是否将图像视为高优先级,并且预加载(preload)。

    • loading=lazy冲突,不能同时使用
    • 应该对检测为LCP(最大内容绘制)元素的任何图像使用 priority 属性。
    • 仅当图像在首屏可见时,才应使用。
  • placeholderblur | empty | data:image/...默认:empty,加载图像时使用的占位符。

    • empty时,则在加载图像时将没有占位符,只有空白区域。
    • blur 时,blurDataURL属性将用作占位符。
    • 对于动态图像,必须提供 blurDataURL 属性。
    • data:image/...时,则该dataURL 将在加载图像时用作占位符。
  • tsx
    <Image
        src="/profile.png"
        width={500}
        height={500}
        alt="Picture of the author"
        fill={true}
        priority={true}
        placeholder="blur"
    />

用法:

▸ 基本使用:加载显示本地图片

需要通过import 导入图片资源后才能使用。同时必须添加alt属性。

image-20241014130322939


▸ 加载显示网络图片

添加网络图片必须手动指定图片的widthheight属性。宽高单位不支持百分比%,必须是 number 类型。

image-20241014131326164

如果图片是外站资源,需要在next.config.js中配置图片的域名白名单。

image-20241014131234164


▸ 在next中使用原生img标签

在 next 中使用原生 img 标签需要用 picture 标签包裹之后使用,目的是方便之后做适配。

image-20241014132006231

如果不想使用 picture 包裹,可以在.eslintrc.json中关闭错误提示。

image-20241014131927628


▸ priority:图片预加载

对于大图可以添加priority预加载图片。

image-20241014132435571

该属性会为渲染后的<link>标签添加rel="preload"属性。

image-20241014132437792


▸ fill:为Image添加百分比尺寸

注意: 使用了 fill 属性后,在渲染时会生成 8 张不同尺寸的图片(有问题:实际生成的是 8 张尺寸相同的图片)。

1、父容器设置为相对定位

image-20241014132754738

2、为 Image 添加 fill 属性

image-20241014132929547

静态资源

全局样式

Next.js 允许在 JavaScript 文件中直接通过 import 关键字来导入 CSS 文件(不是@import )

用法:

▸ 基本使用:导入自定义样式

1、在 assets 目录或 styles 目录下编写。推荐styles目录

image-20241014150710825

image-20241014150723333

2、然后在 pages/_app.tsx 入口组件中导入

image-20241014150736180

3、在组件中使用全局样式

image-20241014151119943


▸ 导入第三方样式

也支持导入node_modules中样式,导入文件后缀名不能省略。

局部样式

Next.js 默认是支持 CSS Module的,如:[name].module.css

CSS Module 中的选择器会自动创建一个唯一的类名。唯一类名保证在不同的文件中使用相同 CSS 类名,也不用担心冲突。

用法:

▸ 基本使用

1、在[name].module.css[name].module.scss样式文件中编写局部样式

  • 样式名的写法可以是camelCasekebab-case

image-20241014151542999

2、在组件中通过import导入样式并使用

  • 使用camelCase命名的样式在使用时可以通过styles.xxxYyy语法访问。
  • 使用kebab-case命名的样式在使用时可以通过styles["xxx-yyy"]语法访问。

image-20241014151558430


▸ 在样式中手动导入样式文件

注意: 该功能全局和局部样式都支持

1、定义样式文件,并在其中定义变量和混入

image-20241014152907396

2、在另一个样式文件中使用@use 手动导入该样式文件并使用定义的变量和混入

注意: @use可以有以下语法:

  • @use './xxx.scss' as *,使用时可以直接使用$gbColor
  • @use './xxx.scss' as xxx ,使用时需要如下使用xxx.$gbColor

image-20241014152934523


▸ 在样式中自动导入样式文件

【目前没有自动导入功能】


▸ 使用:export {}导出变量供 JS 使用

注意: :export {}必须要在.module.css模块文件中使用

1、在xxx.module.scss文件中使用:export {} 导出变量

image-20241014154522582

2、在 JS 中使用import from导入scss文件到模块中,并在 JS 代码中使用

image-20241014154722755

静态资源引用

public

public目录常用于存放静态文件,例如:robots.txtfavicon.icoimginfo.json等,并直接对外提供访问。

访问: 需以/作为开始路径通过静态 URL访问。如添加了一张图片到public/me.png中,通过/me.png访问。

特性:

  • 静态 URL 也支持在背景中使用

  • 确保静态文件中没有与 pages/ 下的文件重名,否则导致错误。

用法:

▸ 基本使用:在 Image 组件中使用

image-20241014155839344


▸ 在背景中使用

image-20241014155915606


▸ 直接在浏览器通过 url 访问public/info.json中的数据

image-20241029170347658

image-20241029170403789

assets

assets目录常用存放样式字体图片SVG 等文件

访问: 需要用 import 导入位于 assets 目录的文件成为模块后使用,支持相对路径和绝对路径。

js
// 相对路径
import Avatar from '../assets/images/avatar.png'

// 绝对路径
import Avatar from '@/assets/images/avatar.png'

特性:

  • 支持在背景图片和字体中使用: url("~/assets/images/bym.png"),不支持@/import等语法。

用法:

▸ 基本使用:使用import导入图片并使用

image-20241014160453479


▸ 在背景中使用

注意: 在背景中比较特殊,只能通过~/开头的绝对路径的静态 URL 导入。不支持@/import等语法。

image-20241014160622977

字体图标

1、将字体图标存放在 assets 目录下

image-20241014160935176

2、字体文件可以使用相对路径和绝对路径引用。

image-20241014161006498

3、在_app.tsx文件中全局导入字体样式

image-20241014161058346

4、在页面中就可以使用字体图标了

image-20241014161152601

路由

API useRouter()

  • useRouter()(),返回一个 router 对象,可以通过调用该对象中的方法实现编程导航。

    • 返回:

    • routerobject,返回一个 router 对象

    • js
      const router = useRouter()
  • router 属性:

  • router:router 对象

    • pathnamestring,当前路由文件的路径。
    • queryobject默认:{},解析为对象的查询字符串,包括动态路由参数。如果页面不使用服务器端渲染,则在预渲染期间它将是一个空对象。
    • basePathstring,活动的 basePath。
    • isReadyboolean,路由器字段是否已在客户端更新并可供使用。只在 useEffect 方法中使用。
    • isPreviewboolean,应用程序当前是否处于预览模式。
    • 其他asPathisFallbacklocalelocalesdefaultLocaledefaultLocales、``、
  • router 方法:

  • router.push()( url, as?, options?),页面跳转。

    • urlUrlObject |string,要导航到的 URL。

    • as?UrlObject |string,路径别名,在浏览器 URL 栏中显示的路径的可选装饰器。

    • options?{scroll,shallow,locale},配置选项。

    • js
      // 普通导航
      router.push('/about');
      
      // 带查询参数的导航
      router.push({ pathname: '/profile', query: { id: 1 } });
      
      // 动态路由
      router.push('/posts/[id]', '/posts/1');
      
      // 使用选项
      router.push('/dashboard', undefined, { shallow: true });
  • router.replace()( url, as?, options?),页面跳转,会替换当前页面

    • urlUrlObject |string,要导航到的 URL。

    • as?UrlObject |string,路径别名,在浏览器 URL 栏中显示的路径的可选装饰器。

    • options?{scroll,shallow,locale},配置选项。

    • js
      // 普通替换导航
      router.replace('/about');
      
      // 带查询参数的替换导航
      router.replace({ pathname: '/profile', query: { id: 1 } });
      
      // 动态路由
      router.replace('/posts/[id]', '/posts/1');
      
      // 使用选项
      router.replace('/dashboard', undefined, { shallow: true });
  • router.back()(),页面返回。

    • js
      router.back();
  • router.events.on()(event, callback),客户端路由的监听,建议在_app.tsx 监听

  • router.events.off()(event, callback),移除客户端路由的监听,建议在_app.tsx 监听

    • eventstring,要监听的事件类型:

      • 'routeChangeStart': fn: (url, as?) => void,当路由开始变化时触发。
      • 'routeChangeComplete': fn: (url, as?) => void,当路由变化完成时触发。
      • 其他:
      • 'routeChangeError': fn: (err, url) => void,当路由变化失败时触发。
      • 'beforeHistoryChange': fn: (url) => void,在浏览器历史记录变化之前触发。
      • 'hashChangeStart': fn: (url, as?) => void,当哈希值开始变化时触发。
      • 'hashChangeComplete': fn: (url, as?) => void,当哈希值变化完成时触发。
    • callbackfunction,当指定的事件触发时调用的函数。这个函数可以接受事件相关的参数。

    • js
      import { useEffect } from 'react';
      import { useRouter } from 'next/router';
      
      const MyComponent = () => {
          const router = useRouter();
      
          const handleRouteChange = (url) => {
              console.log('正在导航到:', url);
          };
      
          useEffect(() => {
              // 添加事件监听器
              router.events.on('routeChangeStart', handleRouteChange);
      
              // 清理监听器
              return () => {
                  router.events.off('routeChangeStart', handleRouteChange);
              };
          }, [router.events]);
      
          return (
              <div>
                  <h1>我的组件</h1>
              </div>
          );
      };
      
      export default MyComponent;
  • router.beforePopState()(callback),路由的返回和前进的监听。建议在_app.tsx 监听

    • callback({url, as?, options?}) => boolean,用于判断是否允许路由更新。

    • 返回:

      • true:允许状态更新,路由将继续进行。
      • false:阻止状态更新,浏览器不会导航到新 URL。
    • tsx
      import { useEffect } from 'react';
      import { useRouter } from 'next/router';
      
      const MyComponent = () => {
          const router = useRouter();
      
          useEffect(() => {
      -        const handleBeforePopState = ({ url }) => {
                  // 在这里添加条件判断,例如:
                  if (url === '/restricted') {
                      alert('您无法返回到这个页面!');
                      return false; // 阻止导航
                  }
                  return true; // 允许导航
              };
      
              // 设置 beforePopState
       +       router.beforePopState(handleBeforePopState);
      
              // 清理
              return () => {
                  router.beforePopState(() => true); // 重置为默认允许导航
              };
          }, [router]);
      
          return (
              <div>
                  <h1>我的组件</h1>
                  <p>尝试后退浏览器历史记录。</p>
              </div>
          );
      };
      
      export default MyComponent;

新建页面

Next.js 项目页面需在 pages 目录下新建.js.jsx.ts.tsx )文件,该文件需导出的 React 组件

约定式路由: Next.js 会根据 pages 目录结构和文件名,来自动生成路由,比如:

  • pages/index.jsx/ (首页, 一级路由)
  • pages/about.jsx/about (一级路由)
  • pages/blog/index.jsx/blog (一级路由)
  • pages/blog/post.jsx/blog/post (嵌套路由,一级路由)
  • pages/blog/[slug].jsx /blog/:slug (动态路由, 一级路由)

注意:

  • Next.js 中没有命令行的方式创建页面。
  • Next.js不需要占位组件,和 Nuxt3 不同。

用法:

▸ 新建页面步骤:

1、新建一个名为 pages/category.jsx 的组件文件,并导出(export)React 组件。

image-20241014162234088

2、通过 /category 路径访问新创建的页面了。

image-20241014162309511


▸ 标准模板:

1、在typescriptreact.json中定义 next + ts 的模板

image-20241014163100514

2、使用nextts创建一个 React 组件

image-20241014163057758

导航

页面之间的跳转需要用到<Link>组件,需从 next/link 包导入。

Link 组件底层实现是一个 <a> 标签,所以使用 a + href 也支持页面切换(不推荐, 会默认刷新浏览器)。

<Link>:可以启用客户端的路由切换,从 next/link 导入。

  • 属性:

  • hrefstring | {pathname:'' , query : {}},要导航到的路径或 URL。

  • replace?boolean默认:false,是否替换当前 url 页面,而不是将新的 url 添加到堆栈中。

  • target?_blank | _self | _parent | _top,指定何种方式显示新页面,同a标签中的target一样。

  • as?string,在浏览器的URL栏显示的路径的别名。

  • prefetchboolean默认:false,当 <Link> 组件进入用户的视区(最初或通过滚动)时,将发生预取。预取仅在 production 中启用

  • 其他:scroll、shallow、locale等

  • tsx
    <Link href="/dashboard">Dashboard</Link>
    <Link href={{ pathname: '/about', query: { name: 'test' }, }}>About</Link>
    
    <Link href="/dashboard" replace>Dashboard</Link>
    
    <Link href="/dashboard" prefetch={false}> Dashboard </Link>

▸ 基本使用:href 值的类型

href 的值支持字符串、对象和 URL 链接的写法

image-20241014164322720


▸ as:为路径起别名

image-20241014164543410

image-20241014164550122

注意: 对于对象类型的 href 写法,同样支持

image-20241014164704479


▸ replace:替换当前 url 页面

image-20241014164850053

编程导航 useRouter()

Next13 除了可以通过<Link>组件来实现导航,同时也支持使用编程导航。

编程导航可以轻松的实现动态导航了,缺点就是不利于 SEO

我们可以从 next/router 中导入 useRouter() 函数(或 class 中用 withRouer()),调用该函数可以拿到 router 对象进行编程导航。

语法API useRouter()

用法:

▸ 参数 url:跳转地址

支持字符串、对象、外部 URL 链接路由

image-20241014170525929


▸ 参数 as:路径别名

image-20241014170905342


▸ router.events.on(name,fn):客户端路由监听

注意:

  • 建议事件监听这种有副作用的方法写到useEffect()生命周期中,保证只在客户端执行。防止在服务端无法结束监听,释放变量。
  • events.on()中没有 nuxt3 中的from, to只有urlurl相当于to

image-20241014171858373

动态路由

Next.js 也是支持动态路由,并且也是根据目录结构和文件的名称自动生成。

语法:

  • 页面组件目录 或 页面组件文件都 支持 [] 方括号语法方括号前后不能有字符串)。
  • 方括号里编写的字符串就是:动态路由的参数。

示例: 动态路由支持如下写法:

  • pages/detail/[id].tsx -> /detail/:id
  • pages/detail/[role]/[id].tsx -> /detail/:role/:id
  • pages/detail-[role]/[id].tsx -> /detail-:role/:id (不支持)

用法:

[]语法前后不能有字符串

sh
# 不支持以下写法
pages/user-[role]/index.tsx
pages/[role]/[id]-name.tsx

▸ 访问多个动态路由参数

路由格式如下:pages/[role]/[id].tsx

image-20241014174803602

路由参数

动态路由参数

传递参数:

1、通过[]方括号语法定义动态路由,比如:/detail/[id].tsx

2、页面跳转时,在 URL 路径中传递动态路由参数,比如:/detail/10010

访问参数: 在目标页面[id].tsx通过 router.query 获取动态路由参数

注意:

  • 此处是query,而不是params
  • 此处是 router, 而不是 route
  • 如果动态路由参数和查询参数名字重复,那么动态路由参数将覆盖同名的查询字符串参数。

image-20241014173805997

查询字符串参数

传递参数: 页面跳转时,通过查询字符串方式传递参数,比如:/post/detail?id=1000

访问参数: 在目标页面通过 router.query 获取查询字符串参数

image-20241014172915253

404 Page

捕获 404 页面的方法:

方式一: [...slug].tsx,捕获所有不匹配的路由,即 404 页面。

通过在方括号内添加三个点 ,如:[...slug].tsx

sh
# 访问 pages/post/[...slug].js
/post/a # 匹配
/post/a/b # 匹配
/post # 不匹配

[...slug] 匹配的参数将作为查询参数发送到页面,并且它始终是一个数组

路由格式如下:pages/detail04/123/sdf

image-20241014181121158

image-20241014181219670

注意:

  • 可以使用除了 slug 以外的名称,如:[...param].tsx
  • [...slug].tsx可以在pages目录或其子目录中。它仅作用于该目录以及子目录。

方式二: 404.tsx,在 pages 根目录新建 404.tsx 页面。(推荐

注意:

  • 404.tsx写法只支持 pages 根目录
  • 还支持 500.tsx 文件,即客户端或者服务器端报错

路由匹配规则

路由匹配优先级: 预定义路由 > 动态路由 > 捕获所有路由

  • 预定义路由:pages/post/create.js
    • 将匹配 /post/create
  • 动态路由 :pages/post/[pid].js
    • 将匹配/post/1, /post/abc 等。
    • 但不匹配 /post/create 、 /post/1/1 等
  • 捕获所有路由:pages/post/[...slug].js
    • 将匹配 /post/1/2, /post/a/b/c 等。
    • 但不匹配/post/create, /post/abc、/post/1、、/post/ 等

中间件

middleware()(req, event?),用于定义中间件。这些中间件可以在请求到达页面之前进行某些处理,例如进行身份验证、重定向等。

  • reqNextRequest,提供了对请求信息的访问。

    • nextUrl:``,请求url(推荐使用)
    • method:``,请求方法
    • headers:``,请求头
    • body:``,请求体(仅适用POST请求)
    • cookies:``,获取cookie
    • ...:``,
  • event?NextFetchEvent

  • 返回:

  • nextResponseNextResponse

    • NextResponse.next():``,向下一个处理中间件或页面传递请求
    • NextResponse.redirect('/path'):``,重定向到另一个路径
    • NextResponse.rewrite():``,重写 URL,可用于配置反向代理
    • NextResponse.json({ message: 'Hello' }):``,返回自定义响应
  • 没有返回值:``,同 NextResponse.next()一样,放行。继续处理下一个中间件。

  • tsx
    import { NextResponse } from 'next/server';
    
    export function middleware(req) {
        const isAuthenticated = /* 检查用户认证的逻辑 */;
    
        if (!isAuthenticated) {
            // 重定向到登录页面
            return NextResponse.redirect(new URL('/login', req.url));
        }
    
        // 允许请求继续
        return NextResponse.next();
    }
    
    // 仅对某些路径应用中间件
    export const config = {
        matcher: ['/protected/:path*'], // 匹配 /protected 下的所有路径
    };

Next.js 的中间件允许我们去拦截客户端发起的请求,如API 请求router 切换资源加载站点图片等。

拦截客户端发起的请求等等之后,便可对这些进行:重写、重定向、修改请求响应标头、或响应等操作。

对比 Nuxt3:

  • Nuxt3中的中间件只能拦截 router 切换,而 Next.js 的中间件可以拦截更多的请求。

特性:

  • 中间件只在服务端运行。

用法:

▸ 基本使用

1、在根目录中创建 middleware.ts 文件

2、从 middleware.ts 文件中导出一个中间件 middleware 函数(支持 async,并只允许在服务端),会接收两个参数

3、通过返回NextResponse对象来实现重定向等功能

image-20241014215536719


▸ 设置 cookie

1、依赖包:cookies-next

sh
pnpm i cookies-next

2、通过 setCookie() 方法设置 cookie

image-20241014220045855

3、在中间件中通过 req.cookies.get('token').value 访问设置的 cookie

image-20241014220159780


▸ 反向代理:通过返回 rewrite()实现反向代理

1、依赖包: axios

2、在组件中向http://localhost:3000发起网络请求

image-20241014221142526

3、在localhost:3000服务器对请求进行重写。向http://codercba.com:9060发送请求

image-20241014221320133

匹配器

匹配器允许我们过滤中间件以应用特定的中间件。

语法:

matcher 可以接受一个字符串、数组或正则表达式,用于匹配请求路径。

  • 匹配字符串

    js
    export const config = {
      matcher: '/about' // 仅匹配 /about 路径
    }
  • 匹配数组

    js
    export const config = {
      matcher: ['/about', '/contact'] // 匹配 /about 和 /contact 路径
    }
  • 匹配正则表达式

    js
    export const config = {
      matcher: /^\/api\/.*$/ // 匹配所有以 /api/ 开头的路径
    }

用法:

▸ 匹配动态路由:使用 :path* 来匹配动态路径部分

js
// middleware.js
import { NextResponse } from 'next/server'

export function middleware(req) {
  // 中间件逻辑...
  return NextResponse.next()
}

export const config = {
  matcher: [
    '/api/:path*', // 匹配 api/* 的路径
    '/dashboard/:path*', // 匹配 dashboard/* 的路径
    '/((?!api|_next/static|favicon.ico).*)' // 匹配不包含 api、_next、static、favicon.ico 的路径
  ]
}

说明:

  • .:表示任意字符
  • *:表示 0 或 n 个
  • :path*:表示匹配动态路由:path开头的路径
  • (?!pattern):表示当前位置后面不跟随 pattern 这个模式。

▸ 路由拦截:使用中间件+匹配器实现路由拦截

image-20240911161224190

布局

Layout

Layout布局是页面的包装器,可以将多个页面的共性东西写到 Layout 布局中,使用 props.children 属性来显示页面内容。

例如:每个页面的页眉页脚组件,这些具有共性的组件我们是可以写到一个 Layout 布局中。


▸ 基本使用:

1、在components目录下新建 layout/index.tsx 布局组件

image-20241016104753617

2、接着在_app.tsx中通过<Layout>组件包裹<Component>组件

image-20241016104859499

3、效果

image-20241014222728991

个性化布局

实现 Cart 页有 Header 和 Footer,其他页没有。

image-20241014223532673

嵌套布局

Layout 布局可以作为所有页面的容器,也可以给每个页面一个单独的布局也是可以的,并且也可以在布局中嵌套布局

因此,我们可以利用布局再嵌套一个布局来实现二级路由


▸ 基本使用:

1、定义嵌套布局

image-20241014224338835

2、在布局<Layout>中嵌套<NestLayout>

image-20241014224342469


▸ 优化: 更加优雅的写法见下面章节

getLayout+TS

1、在组件中添加 getLayout() 静态方法,在其中返回自定义布局

tsx
// pages/index.tsx

import type { ReactElement } from 'react'
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
import type { IStaticProps } from './_app.tsx'

interface IProps {
  children?: ReactElement
}

export const Page: FC<IProps> & IStaticProps = (props) => {
  return {
    /** Your content */
  }
}

// 定义 getLayout()
Page.getLayout = function getLayout(page: ReactElement) {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>
  )
}

2、在_app.tsx中将占位组件传入 getLayout()方法中。并为getLayout()扩展 TS 类型

ts
// pages/_app.tsx

import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'

 export interface IStaticProps {
  getLayout?: (page: ReactElement) => ReactNode
}

+ type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & IStaticProps

 type AppPropsWithLayout = AppProps & {
+  Component: NextPageWithLayout
}

+ export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  // Use the layout defined at the page level, if available
  // 调用 getLayout()
  const getLayout = Component.getLayout ?? ((page) => page)
  return getLayout(<Component {...pageProps} />)
}

嵌套路由

Next.js 和 Nuxt3 一样,也支持嵌套路由(只在 app 目录下),也是根据目录结构和文件的名称自动生成。

二级路由实现有两种方案:

  • 方案一:使用 Layout 布局嵌套来实现
  • 方案二:使用 Next.js 13 版本,新增的 app 目录(目前 beta 版本)

方案一:Layout

在一级页面/profile中添加二级页面/profile/register

1、在二级布局nest-layout.tsx中添加布局

image-20241016114218766

2、在二级页面profile/register.tsx中添加getLayout属性,添加自定义布局

image-20241016114157482

方案二:app目录

Next.js 13 版本,新增的app 目录(目前 beta 版本,还是处于实验性阶段,需要在配置开始)

注意:

  • page.tsxlayout.tsxhead.tsx是固定的文件名,不能修改成其他名称。

  • 创建完page.tsx运行后会自动添加 2 个页面layout.tsxhead.tsx

0、在配置中设置 app 目录为实验性功能

image-20241016120239631

1、创建 app 目录

2、创建根 RootLayout 布局:layout.tsx

image-20241016120132313

3、创建首页: page.tsx

image-20241016121006589

4、创建 head.tsx,定制 head,用于 SEO

image-20241016120115549

5、创建其它页面 cart 和布局

  • page.tsx

    image-20241016120333691

  • layout.tsx

    image-20241016120413700

6、创建 loading.tsx 页面,为加载中的页面添加 loading 效果,可以在其中添加骨架屏

image-20241016175645084

image-20241016175703436

7、目录结构

image-20241016175613918

生命周期

组件生命周期

组件生命周期钩子在客户端服务端触发情况:

class 组件Hooks 组件客户端服务端
constructoruseState
UNSAFE_componentWillMount-
render函数本身
componentDidMountuseEffect
componentWillUnmountuseEffect 中的返回函数
shouldComponentUpdateuseMemo
componentDidUpdateuseEffect
componentDidCatch-
getDerivedStateFromPropsuseState 中的 update
getDerivedStateFromError-

网络请求

axios封装

依赖包:

  • axios
    • 安装:pnpm i axios

基本实现:

1、定义 HYRequest 类,并导出

image-20241017144450955

2、在类中定义 request 方法

  • 定义 request 方法,此时返回的是Promise<unkown>类型的数据,需要添加 TS 类型

    image-20241017144754747

  • 添加 TS 类型

    image-20241017152846068

  • 使用 request()方法请求数据,并传入泛型参数值

    image-20241017152940955

3、定义 get、post 等其他方法

image-20241017153344037

4、拦截器

image-20241017153507156

API Routes

Next.js 提供了编写后端接口的功能(即 API Routes),编写接口可以pages/api 目录下编写

在 pages/api 目录下的任何 API Routes 文件都会自动映射到/api/* 前缀开头接口地址


基本实现:编写一个 /api/user 接口

1、在 pages/api 目录下新建 user.ts

2、在该文件中使用 handler 函数来定义接口

image-20241017154711284

image-20241029170034190

3、然后就可以用 fetch 函数 或 axios 轻松调用: /api/user 接口了

image-20241017154720101

4、区分请求方法

image-20241017155842501

image-20241017155855514

渲染模式

API

  • getStaticProps()()
    • :``,
    • :``,
    • 返回:
    • :``,
  • getStaticPaths()()
    • :``,
    • :``,
    • 返回:
    • :``,
  • getServerSideProps()()
    • :``,
    • :``,
    • 返回:
    • :``,
  • ()
    • :``,
    • :``,
    • 返回:
    • :``,

认识预渲染

预渲染:默认情况下,Next.js 会预渲染每个页面,即预先为每个页面生成 HTML 文件,而不是由客户端 JavaScript 来完成。预渲染可以带来更好的性能和 SEO 效果。

Hydration:当浏览器加载一个页面时,页面依赖 JS 代码将会执行,执行 JS 代码后会激活页面,使页面具有交互性。

Next.js 具有两种形式的预渲染:

  • 静态生成 (推荐):HTML 在 构建时 生成,并在每次页面请求(request)时重用。
  • 服务器端渲染:在 每次页面请求(request)时 重新生成 HTML 页面。

提示:出于性能考虑,相对服务器端渲染,更 推荐 使用 静态生成 。

SSG

静态生成(也称 SSG 或 静态站点生成):如果一个页面使用了 静态生成,在 构建时 将生成此页面对应的 HTML 文件 。

这意味着在生产环境中,运行 next build 时将生成该页面对应的 HTML 文件。然后,此 HTML 文件将在每个页面请求时被重用,还可以被 CDN 缓存

基本实现

在 Next.js 中,你可以静态生成 带有或不带有数据 的页面。接下来我们分别看看这两种情况。

生成不带数据的静态页面:

默认情况下,Next.js 使用 “静态生成” 来预渲染页面但不涉及获取数据。

注意:

  • 此页面在预渲染时不需要获取任何外部数据。
  • 在这种情况下,Next.js 只需在构建时为每个页面生成一个 HTML 文件即可。

▸ 基本实现:

1、新建pages/about.tsx页面,该页面不带有数据

image-20241017162154065

2、执行pnpm run build打包页面,生成不带数据的静态页面

image-20240911161433782

生成需要获取数据的静态页面:

当某些页面需要获取外部数据以进行预渲染,通常有两种情况:

  • 情况一:页面 内容 取决于外部数据:使用 Next.js 提供的 getStaticProps() 函数。
  • 情况二:页面 paths(路径) 取决于外部数据:使用 Next.js 提供的 getStaticPaths() 函数(通常还要同时用 getStaticProps())。

情况一:页面内容取决于外部数据

比如,发起网络请求拿到页面书籍列表的数据,并展示。

具体的使用步骤是:

接口:

  • baseURL

    sh
    http://codercba.com:9060/beike/api
  • 获取书籍列表

    js
    method: POST
    path: /books
    params: {
        page?,
        count?
    }
  • 获取书籍详情

    js
    method: GET
    path: /book/:id
    params: {
        id
    }

1、依赖包:axios

sh
pnpm i axios

2、在service/home.ts中定义fetchBook()方法发送异步请求

image-20241017164848589

3、在组件中定义getStaticProps()方法

  • getStaticProps()函数中借助 axios 获取到数据
  • 拿到异步数据之后 return 给页面组件
  • 页面就可通过 props 拿到数据来渲染页面

image-20241017165107188

5、在 build 时,会执行getStaticProps()方法,经过以上步骤,一个静态页面就打包生成

6、如果想更新该静态页面,需要运行pnpm run build重新打包

image-20241017165118796


情况二:页面 paths(路径)取决于外部数据

例如,新建一个动态路由页面,然后发起网络请求拿到书本列表,然后每本书的信息都使用单独详情页面显示。

简单的理解就是,在 build 阶段时,动态拿到 n 本书,然后根据 n 书动态生成 n 个静态详情页面。

1、在service/home.ts中定义获取书籍详情的异步请求

image-20241017171431459

2、新建pages/books-detail-ssg/[id].tsx页面,根据书籍列表的数量动态生成页面

  • 定义getStaticPaths()方法,返回结果会传递个getStaticProps()方法的参数 context

    image-20241017171302775

  • 定义getStaticProps()方法,返回结果会传递给页面组件方法的参数 props

    image-20241017171354377

  • 在组件函数中根据获取到的动态数据渲染页面

    image-20241017171510007

3、执行pnpm run build命令,打包生成 5 个静态页面

image-20241017171627106

应用场景

建议尽可能使用静态生成(无论有 与 没有数据),因为静态生成的页面可以构建一次,并可由 CDN 提供服。

我们可以为多种类型的页面使用静态生成,包括:

  • 营销页面、官网网站
  • 博客文章、投资组合
  • 电子商务产品列表、帮助和文档

如果在用户请求之前就可以预渲染页面,那么应该选择静态生成。反之,静态生成就不合适了。

例如,页面要显示经常更新的数据,并且页面内容会在每次请求时发生变化,这时可以这样选择:

  • SSR + CSR:静态生成与客户端数据获取结合使用
    • 我们可以跳过预呈现页面的某些部分,然后使用客户端 JS 来填充它们,但是客户端渲染是不利于 SEO 优化的,例如:
      • 在 useEffect 中获取数据,在客户端动态渲染页面
  • SSR:服务器端呈现(也称动态呈现)
    • Next.js 会根据每个请求预呈现一个页面。缺点是稍微慢一点,因为页面无法被 CDN 缓存,但预渲染页面将始终是最新的。

SSR

服务器端渲染(也称 SSR 或 动态渲染):如果页面使用的是 服务器端渲染,则会在 每次页面请求时 重新生成页面的 HTML

要对页面使用服务器端渲染,你需要 export 一个名为 getServerSideProps() 的 async 函数。服务器将在每次页面请求时调用此函数。

基本实现

假设你的某个页面需要预渲染频繁更新的数据(从外部 API 获取)。你就可以编写 getServerSideProps 获取该数据并将其传递给 Page ,如下所示:

1、在pages/books-ssr/index.tsx中定义getServerSideProps()方法,用于在每次请求时为页面获取数据

image-20241017174527495

2、在pages/books-ssr/index.tsx的组件函数中根据获取的数据动态渲染页面

image-20241017174734244

3、效果

image-20241017174759007

路由参数

传递参数:在浏览器访问http://localhost:3000/home/123

获取参数

  • 方式一:在pages/home/[id].tsx页面的 react 函数中通过router.query访问路由参数。

  • 方式二:在pages/home/[id].tsx页面的getServerSideProps()函数的`context.params 访问路由参数

    image-20241029174553043

注意事项

我们知道 getStaticProps 和 getStaticPaths 函数都是在 build 阶段运行,那么 getServerSideProps 函数的运行时机是怎么样的呢?

getServerSideProps 运行时机:

  • 首先,getServerSideProps仅在服务器端运行,从不在浏览器上运行。
  • 如果页面使用 getServerSideProps,则:
    • 当直接通过 URL 请求此页面时,getServerSideProps 在请求时运行,并且此页面将使用返回的 props 进行预渲染
    • 通过 Link 或 router 切换页面来请求此页面时,Next.js 向服务器发送 API 请求,服务器端会运行 getServerSideProps

什么时候该使用 getServerSideProps?

  • 当页面显示的数据必须在请求时获取的,才应使用 getServerSideProps 。
    • 如:页面需要显示经常更新的数据,并且页面内容会在每次请求时发生变化。
  • 如过页面使用了 getServerSideProps 函数,那么该页面将在客户端请求时,会在服务器端渲染,页面默认不会缓存。
  • 如果不需要在客户端每次请求时获取页面数据,那么应该考虑在 客户端动态渲染 或 getStaticProps.

ISR

增量静态再生(ISR,Incremental Static Regeneration):Next.js 除了支持静态生成 和 服务器端渲染,Next.js 还允许在构建网站后创建或更新静态页面

基本实现:

比如我们继续实现前面的案例:

发起网络请求拿到页面书籍列表的数据,并展示。这次我们使用 ISR 渲染模式,让服务器端每隔 5s 重新生成静态书籍列表页面

1、在pages/books-isr/index.tsx中通过 SSG 的方式生成一个静态页面,见:SSG

2、修改getStaticProps()方法,在 return 的对象中添加 revalidate 属性,实现每隔多久动态生成新的静态页面

image-20241017180330299

3、执行pnpm run build命令打包页面

4、执行pnpm run start启动打包后的页面,每隔 5s 会重新生成静态页面

image-20241017180424575

CSR

CSR: Next.js 除了支持在服务器端获取数据,同时也是支持在客户端获取数据,并在客户端进行渲染

在客户端获取数据,需要在页面组件或普通组件的 useEffect() 函数中获取,比如:

1、在pages/books-csr/index.tsx中通过 useEffect()获取数据

image-20241017181229961